<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no" />
    <title>Title</title>
  <style>
    .item {
      display: none;
    }
    #wechat.item {
      display: block;
    }
  </style>
</head>
<body>
    <h3> ***记录微信，qq，微博网页授权的一些过程，
      以及各平台的一些坑;</h3>
    <h6>
      ** 环境以及使用框架  win10 , node-express;
    </h6>
    <pre>
            ***注册各平台应用程序，得到相应的 appid及secret ，注册过程略
            （
              #a. 微信只要有一个公众号账号可以
              可以使用测试账号【测试账号拥有大部分权限，且相关接口无调用限制】;
              #b. 微信测试账号填写的成功回调可支持端口号配置，但是到正式环境不能使用端口号，
                所以建议最开始就用子目录去配置回调地址；
              #c. 微博，QQ则都支持子目录和端口，并且支持多个回调地址，以'逗号分隔'；
              ）;
          </pre>
    <div>
      （注：三个平台均没有做token缓存）
    </div>
    <div>
      <ul>
        <li>
          <a href="#wechat">微信</a>
        </li>
        <li>
          <a href="#sina">微博</a>
        </li>
        <li>
          <a href="#qq">QQ</a>
        </li>
      </ul>
    </div>
    <br/>
    <div id="wechat" class="item">
      <h3>#微信</h3>
      <strong>##1. 指引用户到授权引导页</strong>
      <pre>
        /*
        * {userId} 我的业务参数，需要通过引导页传递给自己的业务页面的；
        * {appid}  注册的应用id,测试账号也有自己的appid,不要弄混；
        * {url}    授权成功后将跳转到的页面，也就是自己的业务页面；
        * @return  给用户指引的完整路径地址；
        */
        注：微信网页授权scope 分两种，本文采取是需用户手动同意的授权方式，scope=snsapi_userinfo；
        另一种静默授权为 scope=snsapi_base 只能获取用户的openid ，无法获取用户详情；
        具体参考微信公众号 网页授权机制文档；
        var wxUrl = function (appid,url,userId) {
            var redirect_uri = url;
			    return 'https://open.weixin.qq.com/connect/oauth2/authorize?appid='+
				    appid +'&redirect_uri='+ redirect_uri +'&response_type=code&' +
				    'scope=snsapi_userinfo&state='+ userId +'#wechat_redirect'
        }

        授权成功后的url 跳转路径为： redirect_uri + code + state;
        列 : shop.zhumey.com/share/work?code={CODE}&sate={USERID};
      </pre>
      <strong>##2.请求微信 acesstoken(网页授权accesstoken 和 微信acesstoken 是两个东西)</strong>
      <pre>
          const request = require('request')
          var wxconfig = require('../appConfig').wxconfig;
          var getAccessTokenUrl = wxconfig.accessTokenUrl;
          <strong>
           // accessTokenUrl: 'https://api.weixin.qq.com/sns/oauth2/access_token';
          </strong>
          var getAccessToken = function(code) {
              return new Promise(function (resolve, reject) {
                      request.get({
                          uri: getAccessTokenUrl,
                          json: true,
                          qs: {
                              grant_type: 'authorization_code',
                              appid: wxconfig.appid,    // APPID请换成你的 appid
                              secret: wxconfig.secret,    // APPSECRET请换成你的 appsecret
                              code:code
                          }
                      }, (err, res, body) => {
                          if (err) {
                              reject(err)
                          }
                          // err exp : {"errcode":40003,"errmsg":" invalid code "}
                          if (body.errcode) {
                              reject(body.errcode + body.errmsg)
                           }
                          resolve(body)
                  })
              })
          }
          module.exports = getAccessToken;
        {code} 就是授权页授权成功会获得一个成功的code;
      </pre>

      <strong>##3.获取微信userInfo</strong>
      <pre>
        // https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
        const request = require('request')
        var getAccessToken = require('./getWxaccesstoken'); //获取token的方法
        var wxconfig = require('../appConfig').wxconfig;
        var wxInfoUrl = wxconfig.wxInfoUrl;
        <strong>
          // wxInfoUrl : 'https://api.weixin.qq.com/sns/userinfo';
        </strong>

        var getWxuserInfo = function(code) {
            return new Promise(function (resolve,reject) {
                    getAccessToken(code).then((response) => {
                        request.get({
                            uri: wxInfoUrl,
                            json: true,
                            qs: {
                                access_token: response.access_token, //获取token接口返回的 acesstoken字符串
                                openid:response.openid,  //获取token接口返回的 用户 openid
                                lang:'zh_CN'
                            }
                        }, (err, res, body) => {
                            if (err) {
                                reject(err)
                            }
                            // err exp : {"errcode":40003,"errmsg":" invalid openid "}
                            if (body.errcode) {
                                reject(body.errcode + body.errmsg)
                            }
                            resolve(body)
                        })
                    })
            })
        }
        // 只要向外暴露一个获取值的方法就可以了
        module.exports = getWxuserInfo

        到这一步就已经获取到了用户的详情信息了，关于unionid 需要绑定开放平台，就能在返回值里获取；
      </pre>

      <strong>##4.配置路由接口，在业务页面通过 授权成功获得code为参数，调用上面的getWxuserInfo方法获取详情</strong>
      <pre>
        var getWxuserInfo = require('../src/getWxuserInfo');
        //包装返回对象
        var getResponseObj = function (openid,nickname,headimgurl) {
            var obj = new Object();
            obj['openid'] = openid ;
            obj['nickname'] = nickname ;
            obj['head_img'] = headimgurl ;
            return obj;
        }
        router.get('/work/Wechat', function(req, res, next) {
            var code = req.query.code;
            getWxuserInfo(code).then(response=>{
                //我只取了openid,头像，昵称；
                var BuserData =  getResponseObj(response.openid,response.nickname,response.headimgurl);
                res.json({ code:1,data:BuserData,msg:'获取成功'});
            }).catch(err=>{
                res.json({code:-1,msg:err});
            })
        });
      </pre>

      <strong>##5.在业务页面通过ajax请求路由地址</strong>
      <pre>
        $.getJSON('/share/work/Wechat?code='+code,function (info) {
                        var data = info.data;
                        if(info.code == 1){
                            console.log(data);
                        }else {
                            console.log(data.msg);
                        }
                    })
      </pre>
    </div>

    <div id="sina" class="item">
      <h3>#微博</h3>
      <strong>##1. 指引用户到授权引导页</strong>
      <pre>
        /*
        * {userId} 我的业务参数，需要通过引导页传递给自己的业务页面的；
        * {appid}  注册的应用id；
        * {url}    授权成功后将跳转到的页面，也就是自己的业务页面；
        * @return  给用户指引的完整路径地址；
        */
        var wbUrl = function (appid,url,userid) {
            return 'https://api.weibo.com/oauth2/authorize?client_id='+
                appid +'&response_type=code&redirect_uri='+url+'&state='+ userid
        }
        授权成功后的url 跳转路径为： redirect_uri + code + state;
        列 : shop.zhumey.com/share/work?code={CODE}&sate={USERID};
      </pre>
      <strong>##2.请求微博 acesstoken</strong>
      <pre>
          const request = require('request')
          var config = require('../appConfig').sinaconfig;
          <strong>
            // config.accessTokenUrl : 'https://api.weibo.com/oauth2/access_token'
          </strong>
          <h6>
            注：微博网页授权的一个大坑，这里请求acesstoken必须使用get和post结合传值，方法用
            post请求，data传一个空对象，真正的参数拼接到url里，就像get请求一样，不然会报错；
          </h6>
          var getAccessToken_wb = function(code) {
              return new Promise(function (resolve, reject) {
                  var url = config.accessTokenUrl+'?grant_type=authorization_code&client_id='+config.appid
                  +'&client_secret='+config.secret + '&redirect_uri='+ config.redirect_uri + '&code=' + code
                  request.post(url,{}, (err, res, body) => {
                      console.log(body);
                      if (err) {
                          reject(err)
                      }
                      if (body.errcode) {
                          reject(body.errcode + body.errmsg)
                      }
                      resolve(body)
                  })
              })
          }
          // 只要向外暴露一个获取值的方法就可以了
          module.exports = getAccessToken_wb
      </pre>

      <strong>##3.请求微博 userInfo </strong>
      <pre>
        var request = require('request');
        var getWbaccesstoken = require('../src/getWbaccesstoken');
        var config = require('../appConfig').sinaconfig;
        var infoUrl = config.wbInfoUrl;
        <strong>
            // infoUrl : 'https://api.weibo.com/2/users/show.json';（微博网页授权文档里没找到这个接口，在其他api文档里找到的，坑点）
        </strong>

        var getWbInfo = function(code) {
            return new Promise(function (resolve, reject) {
                getWbaccesstoken(code).then(function (data) {
                      // data.uid 相当于 微信的openid ，用户唯一标识;
                       var data = JSON.parse(data);
                       var url = infoUrl+'?access_token='+data.access_token +'&uid='+data.uid;
                       request.get(url, (err, res, body) => {
                           if (err) {
                               reject(err)
                           }
                           if (body.errcode) {
                               reject(body.errcode)
                           }
                           resolve(body);
                       })
                   })
            })
        }
        // 只要向外暴露一个获取值的方法就可以了
        module.exports = getWbInfo
      </pre>

      <strong>##4.配置路由接口，在业务页面通过 授权成功获得code做为参数</strong>
      <pre>
        var getWbuserInfo = require('../src/getWbuserInfo');
        //包装返回对象
        var getResponseObj = function (openid,nickname,headimgurl) {
            var obj = new Object();
            obj['openid'] = openid ;
            obj['nickname'] = nickname ;
            obj['head_img'] = headimgurl ;
            return obj;
        }
        router.get('/work/SinaWeibo', function(req, res, next) {
          var code = req.query.code;
          getWbuserInfo(code).then(response=>{
              var data = JSON.parse(response);
              var BuserData = getResponseObj(data.id,data.name,data.avatar_large )
              res.json({ code:1,data:BuserData,msg:'获取成功'});
          }).catch(err=>{
              res.json({code:-1,msg:err});
          })
      });
      </pre>

      <strong>##5.在业务页面通过ajax请求路由地址</strong>
      <pre>
        $.getJSON('/share/work/SinaWeibo?code='+code,function (info) {
                        var data = info.data;
                        if(info.code == 1){
                            console.log(data);
                        }else {
                            console.log(data.msg);
                        }
                    })
      </pre>
    </div>

    <div id="qq" class="item">
      <h3>#QQ</h3>
      <strong>##1. 指引用户到授权引导页</strong>
      <pre>
        <h6>
        注：QQ网页授权和其他两个不一样的地方是，其他两个平台通过获取acesstoken接口能直接获取对应的openid或uuid,然后将
        acesstoken和openid或uid做为参数去获取userinfo;,而QQ的则是先通过code获取 acesstoken;再通过acesstoken去获取openid,
        再将acesstoken和openid去获取userInfo;
      </h6>
      </pre>
      <pre>
        /*
        * {userId} 我的业务参数，需要通过引导页传递给自己的业务页面的；
        * {appid}  注册的应用id；
        * {url}    授权成功后将跳转到的页面，也就是自己的业务页面；
        * @return  给用户指引的完整路径地址；
        */
        var qqUrl = function (appid,url,userid) {
            return 'https://graph.qq.com/oauth2.0/authorize?client_id='+
                appid +'&response_type=code&redirect_uri='+url+'&state='+ userid
        }
        授权成功后的url 跳转路径为： redirect_uri + code + state;
        列 : shop.zhumey.com/share/work?code={CODE}&sate={USERID};
      </pre>
      <strong>##2.请求qq授权 acesstoken</strong>
      <pre>
          const request = require('request')
          var config = require('../appConfig').qqconfig;
          <h6>
            /*
          *
          * https://graph.qq.com/oauth2.0/token?grant_type=authorization_code&
          * client_id=[YOUR_APP_ID]&client_secret=[YOUR_APP_Key]&code=[The_AUTHORIZATION_CODE]
          * &state=[The_CLIENT_STATE]&redirect_uri=[YOUR_REDIRECT_URI]
          *
          * */
          </h6>
          var getAccessToken_qq = function(code) {
              return new Promise(function (resolve, reject) {
                  var url = config.accessTokenUrl+'?grant_type=authorization_code&client_id='+config.appid
                      +'&client_secret='+config.secret + '&redirect_uri='+ config.redirect_uri + '&code=' + code
                  request.get(url, (err, res, body) => {
                      if (err) {
                          reject(err)
                      }
                      if (body.errcode) {
                          reject(body.errcode + body.errmsg)
                      }
                      resolve(body)
                  })
              })
          }
          // 只要向外暴露一个获取值的方法就可以了
          module.exports = getAccessToken_qq

      </pre>

      <strong>##3.请求qq openid </strong>
      <pre>
        const request = require('request')
        var config = require('../appConfig').qqconfig;
        var getQqaccesstoken = require('./getQqaccesstoken');
        var getQueryString = require('./transqqBackData2obj'); 将url参数字符串转成对象

        <h6>
          /*
          * https://graph.qq.com/oauth2.0/me?access_token=YOUR_ACCESS_TOKEN
          */
        </h6>

        var getAccessToken_qq = function(code) {
            return new Promise(function (resolve, reject) {
                getQqaccesstoken(code).then(function (response) {
                <strong>
                  //这里的返回值  是一个url字符串，需要转换成对象才能取值
                </strong>
                    var data = getQueryString(response);
                    var url = config.openidUrl+'?access_token='+data.access_token
                    request.get(url, (err, res, body) => {
                        if (err) {
                            reject(err)
                        }
                        if (body.errcode) {
                            reject(body.errcode + body.errmsg)
                        }
                        var tempObj = {};
                        tempObj['func'] = body;  // boby => callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} );
                        tempObj['access_token'] = data.access_token;
                        resolve(tempObj)
                    })
                })
            })

        }
        // 只要向外暴露一个获取值的方法就可以了
        module.exports = getAccessToken_qq

      </pre>

      <strong>##4.获取qq userInfo</strong>
      <pre>
        const request = require('request')
        var config = require('../appConfig').qqconfig;
        var getQqOpenid = require('./getQqOpenid');
        /*
        * https://graph.qq.com/user/get_user_info?access_token=YOUR_ACCESS_TOKEN&
        * oauth_consumer_key=YOUR_APP_ID&openid=YOUR_OPENID
        */
        var getUserInfo_qq = function(code) {
            return new Promise(function (resolve, reject) {
                getQqOpenid(code).then(function (response) {
                    var func = response['func'];
                    var access_token = response['access_token'];
                    <strong>
                        /*
                        * 这里response['func']是获取qq的openid的方法包装回来的返回值  boby => callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} );
                        * 是一个类似于 jsonp的函数调用，参数才是要取的值，由于不改变业务页面的调用方式，这里采用字符串分隔获取；
                        */
                    </strong>
                    var data = JSON.parse(func.split('(')[1].split(')')[0]);
                    var url = config.qqInfoUrl+'?access_token='+access_token+
                        '&oauth_consumer_key='+config.appid + '&openid='+data.openid
                    request.get(url, (err, res, body) => {
                        var body = JSON.parse(body);
                        if (err) {
                            reject(err)
                        }
                        if (body.errcode) {
                            reject(body.errcode + body.errmsg)
                        }
                        //这里直接包装出去；
                        var tempObj = {};
                        tempObj['openid'] = data.openid ;
                        tempObj['nickname'] = body.nickname ;
                        tempObj['head_img'] = body.figureurl_qq_2 ;
                        resolve(tempObj)
                    })
                })
            })

        }
        // 只要向外暴露一个获取值的方法就可以了
        module.exports = getUserInfo_qq

      </pre>

      <strong>##5.配置路由接口，在业务页面通过 授权成功获得code做为参数</strong>
      <pre>
        var getQqInfo = require('../src/getQqInfo');
        router.get('/work/QQ', function(req, res, next) {
            var code = req.query.code;
            getQqInfo(code).then(response=>{
                res.json({ code:1,data:response,msg:'获取成功'});
            }).catch(err=>{
                res.json({code:-1,msg:err});
            })
        });
      </pre>

      <strong>##6.在业务页面通过ajax请求路由地址</strong>
      <pre>
        $.getJSON('/share/work/QQ?code='+code,function (info) {
                        var data = info.data;
                        if(info.code == 1){
                            console.log(data);
                        }else {
                            console.log(data.msg);
                        }
                    })
      </pre>
    </div>
</body>
</html>
<script>
    /*
    test new mac
    */
  window.onload = function () {
    changeHash();
    window.addEventListener('hashchange',function () {
      changeHash()
    })

    function changeHash() {
      var hash = location.hash.split('#')[1] || 'wechat';
      var items = document.getElementsByClassName('item');
      for(var i = 0;i<items.length;i++){
        items[i].style.display = 'none';
      }
      document.getElementById(hash).style.display = 'block';
    }
  }
</script>
